/* Copyright Florian Schweitzer
 * File created 04.12.2012
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */
package org.openuat.android.service.connectiontype;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.openuat.android.OpenUAT_ID;
import org.openuat.android.channel.main.bluetooth.AndroidRFCOMMServer;
import org.openuat.android.service.OpenUATService;
import org.openuat.android.service.RegisteredApp;
import org.openuat.channel.main.RemoteConnection;

import com.google.common.collect.HashBiMap;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Parcelable;
import android.util.Log;

/**
 * Class for discovering devices using Bluetooth
 * @author Florian Schweitzer
 *
 */
public final class Bluetooth extends IConnectionType {

	/**
	 * Bidirectional hash map with the available clients (OpenUAT_ID and the
	 * corresponding BluetoothDevice)
	 */
	protected HashBiMap<OpenUAT_ID, RemoteConnection> availableClients = null;

	/** The single Bluetooth-instance. */
	private static Bluetooth mInstance = null;

	/**
	 * The local Bluetooth adapter
	 */
	private BluetoothAdapter mBTAdapter = null;

	/**
	 * All found Bluetooth devices in range
	 */
	private List<BluetoothDevice> foundBluetoothDevices = null;

	/**
	 * All found Bluetooth devices in range which have installed the OpenUAT service
	 */
	private static List<BluetoothDevice> foundOpenUATBluetoothDevices = null;

	/** The registered services. */
	private List<RegisteredApp> mServices = null;
	
	/**
	 * The discover trigger of the discovering thread
	 */
	private volatile Thread mDiscoverTrigger = null;
	
	/**
	 * Runnable for start the discovering
	 */
	private final Runnable mDiscoverRunnable = new Runnable() {
		Thread thisThread = null;

		@Override
		public void run() {
			thisThread = Thread.currentThread();
			Log.i(this.toString(), "thread started");
			while (thisThread == mDiscoverTrigger) {
				thisThread.setName("BluetoothDiscovery-Thread");
				try {
					if (!mBTAdapter.isDiscovering()) {
						mBTAdapter.startDiscovery();
						Log.i(this.toString(),"started discovery in thread");						
					}
					
					Thread.sleep(16000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	};
	

	/**
	 * Constructor
	 */
	private Bluetooth() {
		availableClients = HashBiMap.create();
		mServices = new ArrayList<RegisteredApp>();
		foundBluetoothDevices = new ArrayList<BluetoothDevice>();
		foundOpenUATBluetoothDevices = new ArrayList<BluetoothDevice>();

		mBTAdapter = BluetoothAdapter.getDefaultAdapter();
		if (mBTAdapter == null) {
			// Device does not support Bluetooth
			Log.i(this.toString(), "No bluetooth support");
			return;
		}

		if (!mBTAdapter.isEnabled()) {
			mBTAdapter.enable();
		}

		// Register the BroadcastReceiver
		IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
		filter.addAction(BluetoothDevice.ACTION_UUID);
		filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
		filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
		OpenUATService.context.registerReceiver(ActionFoundReceiver, filter);
	}

	/**
	 * Gets the Bluetooth devices which have installed the OpenUAT service
	 * @return
	 */
	public static List<BluetoothDevice> getFoundOpenUATDevices() {
		return foundOpenUATBluetoothDevices;
	}
	
	/**
	 * Adds an available client to connect to
	 * @param id		The OpenUAT_ID of the available client
	 * @param remote	The remote connection to the available client
	 */
	public void addAvailableClient(OpenUAT_ID id, RemoteConnection remote) {
		if (!availableClients.containsKey(id)) {
			availableClients.put(id, remote);
			
			if (id != null && remote != null) {
				Log.i(this.toString(),"added id '" + id.toString() + "' with remoteconnection '" + remote.toString() +
				"'to available client list");				
			}
			
			if (id == null) {
				Log.i(this.toString(),"added available client with id = null");				
			}
			
			if (remote == null) {
				Log.i(this.toString(),"added available client with remote = null");				
			}
			
		}
	}

	/**
	 * The BroadcastReceiver for discovering
	 */
	private final BroadcastReceiver ActionFoundReceiver = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {

			String action = intent.getAction();

			if (BluetoothDevice.ACTION_FOUND.equals(action)) {

				BluetoothDevice device = intent
						.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
				// out.append("\n  Device: " + device.getName() + ", " +
				// device);
				Log.i(this.toString(), "Device: " + device.getName() + ", "
						+ device);
				// btDeviceList.add(device);
				if (!foundBluetoothDevices.contains(device)) {
					foundBluetoothDevices.add(device);
				}

			} else {

				if (BluetoothDevice.ACTION_UUID.equals(action)) {

					BluetoothDevice device = intent
							.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
					Parcelable[] uuidExtra = intent
							.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);

					if (uuidExtra != null) {
						for (int i = 0; i < uuidExtra.length; i++) {
							// Log.i(this.toString(), "Device: " +
							// device.getName() + ", " + device + ", Service: "
							// + uuidExtra[i].toString());

							if (uuidExtra[i].toString().equalsIgnoreCase(
									AndroidRFCOMMServer.serviceUUIDString)) {
								if (!foundOpenUATBluetoothDevices
										.contains(device)) {
									foundOpenUATBluetoothDevices.add(device);
									Log.i(this.toString(),
											"Discovery found Bluetooth device '"
													+ device.getName()
													+ "' with OpenUAT-Service and added it to list");
								}
							}
						}
					}

				} else {

					if (BluetoothAdapter.ACTION_DISCOVERY_STARTED
							.equals(action)) {

						Log.i(this.toString(), "\nDiscovery Started...");
					} else {

						if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED
								.equals(action)) {

							Log.i(this.toString(), "\nDiscovery Finished");
							Iterator<BluetoothDevice> itr = foundBluetoothDevices
									.iterator();

							while (itr.hasNext()) {
								// Get Services for paired devices
								BluetoothDevice device = itr.next();
								Log.i(this.toString(),
										"\nGetting Services for "
												+ device.getName() + ", "
												+ device);

								if (!device.fetchUuidsWithSdp()) {
									Log.i(this.toString(), "\nSDP Failed for "
											+ device.getName());
								}

							}
						}
					}
				}
			}
		}
	};

	/**
	 * Gets the single instance of Bluetooth.
	 * 
	 * @return single instance of Bluetooth
	 */
	public static Bluetooth getInstance() {
		if (Bluetooth.mInstance == null) {
			Bluetooth.mInstance = new Bluetooth();
		}
		return Bluetooth.mInstance;
	}

	@Override
	public void addApp(RegisteredApp app) {
		if (!mServices.contains(app)) {
			mServices.add(app);
		}
	}

	@Override
	public void removeApp(RegisteredApp app) {
		if (mServices.contains(app) && (app.getNumberOfClients() == 0)) {
			mServices.remove(app);
		}
	}

	@Override
	protected void close() {
		cancelDiscovery();
		OpenUATService.context.unregisterReceiver(ActionFoundReceiver);		
		mBTAdapter.disable();
		
		availableClients.clear();
		foundBluetoothDevices.clear();
		foundOpenUATBluetoothDevices.clear();
		
		mInstance = null;
	}
	
	/**
	 * Cancels the discovery
	 */
	public void cancelDiscovery() {
		mDiscoverTrigger = null;
		mBTAdapter.cancelDiscovery();		
	}

	@Override
	protected void open() {        
		if (mDiscoverTrigger == null) {
			mDiscoverTrigger = new Thread(mDiscoverRunnable);			
		}
		mDiscoverTrigger.start();
		
		Log.i(this.toString(), "bluetooth discovery opened");
	}
}
